﻿using LuckyShell.ShellOrders;
using LuckyShell.ShellValues;
using sly.lexer;
using sly.parser.generator;
using sly.parser.parser;

namespace LuckyShell.Parsers;

[ParserRoot("root")]
public class ShellParser
{
    [Production("root: statement*")]
    public ShellTree Root(List<ShellTree> statement) => new BlockOrder(statement);

    [Production("statement: LPAREN[d] statement RPAREN[d]")]
    public ShellTree LrBlock(ShellOrder statement) => statement;

    [Production("statement: PASS[d]")]
    public ShellTree Pass() => new PassOrder();

    #region expr

    [Operation((int)ShellTokenGeneric.LESSER, Affix.InFix, Associativity.Right, 10)]
    [Operation((int)ShellTokenGeneric.GREATER, Affix.InFix, Associativity.Right, 10)]
    [Operation((int)ShellTokenGeneric.EQUALS, Affix.InFix, Associativity.Right, 10)]
    [Operation((int)ShellTokenGeneric.DIFFERENT, Affix.InFix, Associativity.Right, 10)]
    public ShellTree BinaryExpression(ShellTree left, Token<ShellTokenGeneric> operatorToken,
        ShellTree right) =>
        new ShellOperation((left as ShellValue)!, operatorToken.TokenID, (right as ShellValue)!);

    [Operation((int)ShellTokenGeneric.CONCAT, Affix.InFix, Associativity.Right, 100)]
    public ShellTree DotExpr(ShellTree left, Token<ShellTokenGeneric> opera, ShellTree right) =>
        new ShellOperation((left as ShellValue)!, opera.TokenID, (right as ShellValue)!);

    [Operation((int)ShellTokenGeneric.PLUS, Affix.InFix, Associativity.Right, 20)]
    [Operation((int)ShellTokenGeneric.MINUS, Affix.InFix, Associativity.Right, 20)]
    public ShellTree NumberOpera1(ShellTree left, Token<ShellTokenGeneric> opera, ShellTree right) =>
        new ShellOperation((left as ShellValue)!, opera.TokenID, (right as ShellValue)!);

    [Operation((int)ShellTokenGeneric.TIMES, Affix.InFix, Associativity.Right, 70)]
    [Operation((int)ShellTokenGeneric.DIVIDE, Affix.InFix, Associativity.Right, 70)]
    public ShellTree NumBerOpera2(ShellTree left, Token<ShellTokenGeneric> opera, ShellTree right) =>
        new ShellOperation((left as ShellValue)!, opera.TokenID, (right as ShellValue)!);

    [Operation((int)ShellTokenGeneric.AND, Affix.InFix, Associativity.Right, 1)]
    [Operation((int)ShellTokenGeneric.OR, Affix.InFix, Associativity.Right, 1)]
    public ShellTree BoolOpera(ShellValue left, Token<ShellTokenGeneric> opera, ShellValue right) =>
        new ShellOperation(left, opera.TokenID, right);

    [Operation((int)ShellTokenGeneric.NOT, Affix.PreFix, Associativity.Right, 100)]
    public ShellTree NotBool(Token<ShellTokenGeneric> opera, ShellValue expr) =>
        new ShellOperation(null!, opera.TokenID, expr);

    [Operation((int)ShellTokenGeneric.MINUS, Affix.PreFix, Associativity.Right, 100)]
    public ShellTree Negative(Token<ShellTokenGeneric> opera, ShellValue expr) =>
        new ShellOperation(null!, opera.TokenID, expr);

    #endregion

    #region primany

    [Operand]
    [Production("operand: primary")]
    public ShellTree Operand(ShellTree prim) => prim;

    [Production("primary: STRING")]
    public ShellTree String(Token<ShellTokenGeneric> token) =>
        new StringValue(token.Value[1..^1]);

    [Production("primary: DOLLAR[d] STRING")]
    public ShellTree Interpolate(Token<ShellTokenGeneric> token) => new InterpolateStringValue(token.Value[1..^1]);

    [Production("primary: DOUBLE")]
    public ShellTree Double(Token<ShellTokenGeneric> token) => new NumberValue((decimal)token.DoubleValue);

    [Production("primary: INT")]
    public ShellTree Int(Token<ShellTokenGeneric> token) => new NumberValue(token.IntValue);

    [Production("primary: IDENTIFIER")]
    public ShellTree Identifier(Token<ShellTokenGeneric> id) => new IdentifierValue(id.Value);

    [Production("primary: TRUE[d]")]
    public ShellTree BoolTrue() => new BoolValue(true);

    [Production("primary: FALSE[d]")]
    public ShellTree BoolFalse() => new BoolValue(false);

    [Production("primary: IDENTIFIER LPAREN[d] ShellParser_expressions* RPAREN[d]")]
    public ShellTree Instantiate(Token<ShellTokenGeneric> funcName,
        List<ShellTree> value)
    {
        var a = value.OfType<ShellValue>().ToList();
        return new InstantiateValue(funcName.Value, a);
    }

    [Production("primary: DIS_SET[d] STRING")]
    public ShellTree OrderValue(Token<ShellTokenGeneric> token) => new AppValue(token.Value[1..^1]);

    [Production("primary: L_BRACES[d] ShellParser_expressions* R_BRACES[d]")]
    public ShellTree List(List<ShellTree> list)
    {
        return new ListValue(list.OfType<ShellValue>().ToList());
    }

    [Production("primary: L_BRACKET[d] ShellParser_expressions* R_BRACKET[d]")]
    public ShellTree Array(List<ShellTree> list)
    {
        return new ArrayValue(list.OfType<ShellValue>().ToList());
    }

    [Production("primary: LPAREN[d] ShellParser_expressions COMMA[d] ShellParser_expressions RPAREN[d]")]
    public ShellTree Tuple(ShellValue v1, ShellValue v2) => new TupleValue(v1, v2);


    [Production("primary: L_BRACES[d] tuple+ R_BRACES[d]")]
    public ShellTree Dictionary(List<ShellTree> a)
    {
        var b = a.OfType<TupleValue>().ToList();
        return new DictionaryValue(b);
    }

    [Production("tuple: ShellParser_expressions COLON[d] ShellParser_expressions")]
    public ShellTree DicTuple(ShellValue v1, ShellValue v2) => new TupleValue(v1, v2);

    [Production("primary: IDENTIFIER (ARG ShellParser_expressions SEMICOLON[d])*")]
    public ShellTree CommandValue(Token<ShellTokenGeneric> id, List<Group<ShellTokenGeneric, ShellTree>> value)
    {
        var v = value.Select(x => (ShellValue)x.Value(1)).ToList();
        return new CommandValue(id.Value, v);
    }

    #endregion

    [Production("statement: DIS_SET[d] STRING")]
    public ShellTree AppShellOrder(Token<ShellTokenGeneric> token)
        => new AppOrder(token.Value[1..^1]);

    [Production("statement: IDENTIFIER SET[d] ShellParser_expressions")]
    public ShellTree Set(Token<ShellTokenGeneric> id, ShellValue value) =>
        new SetOrder(new IdentifierValue(id.Value), value);

    [Production("statement : IF[d] if_block (ELIF[d] if_block)* (ELSE[d] block)?")]
    public ShellTree IfTree(IfBlock ifBlock, List<Group<ShellTokenGeneric, ShellTree>> elif,
        ValueOption<Group<ShellTokenGeneric, ShellTree>> Else)
    {
        var eGrp = Else.Match(
            x => x, () => null!);
        var elseBlock = eGrp?.Value(0) as BlockOrder;
        var a = elif.Select(x => (IfBlock)x.Value(0)).ToList();
        return new IfOrder(ifBlock, a, elseBlock);
    }

    [Production("if_block: ShellParser_expressions block")]
    public ShellTree IfBlock(ShellValue expr, BlockOrder blockStatement) =>
        new IfBlock(expr, blockStatement);

    [Production("block: INDENT[d] statement* UINDENT[d]")]
    public ShellTree Block(List<ShellTree> statements) => new BlockOrder(statements);

    [Production("statement: FOR[d] statement COMMA[d] ShellParser_expressions COMMA[d] statement block")]
    public ShellTree For(ShellOrder setStatement, ShellOperation expr, ShellOrder statement,
        BlockOrder blockStatement) =>
        new ForOrder(setStatement, expr, statement, blockStatement);

    [Production("statement: WHILE[d] ShellParser_expressions block")]
    public ShellTree While(ShellValue expr, BlockOrder blockStatement) => new WhileOrder(expr, blockStatement);

    [Production(
        "statement: FUNC[d] IDENTIFIER LPAREN[d] IDENTIFIER* RPAREN[d] block (INDENT[d] RETURN ShellParser_expressions UINDENT[d])?")]
    public ShellTree Func(Token<ShellTokenGeneric> id, List<Token<ShellTokenGeneric>> a, BlockOrder blockOrder,
        ValueOption<Group<ShellTokenGeneric, ShellTree>> returnExpr)
    {
        var r = returnExpr.Match(x => x, () => null!)?.Value(0) as ShellValue;
        var arg = a.Select(variable => new IdentifierValue(variable.Value)).ToList();
        return new FuncOrder(id.Value, arg, blockOrder, r);
    }

    [Production("statement: IDENTIFIER LPAREN[d] ShellParser_expressions* RPAREN[d]")]
    public ShellTree FuncInstantiate(Token<ShellTokenGeneric> funcName, List<ShellTree> value)
    {
        var a = value.OfType<ShellValue>().ToList();
        return new InstantiateOrder(funcName.Value, a);
    }

    [Production("statement: IDENTIFIER (ARG ShellParser_expressions SEMICOLON[d])*")]
    public ShellTree Command(Token<ShellTokenGeneric> id, List<Group<ShellTokenGeneric, ShellTree>> value)
    {
        var v = value.Select(x => (ShellValue)x.Value(1)).ToList();
        return new CommandOrder(id.Value, v);
    }
}